/**
 * @file StreamAnalyzer.c
 *
 * This file implements the main process of the application.
 *
 * @author Luc Fourestier
 * @date 2009
 *
 * @mainpage StreamAnalyzer
 *
 * The StreamAnalyzer is a tool to receive and analyze Transport Stream (MPEG).
 * The user can select the different input and output type it wants (file, UDP, TCP...) and so
 * have a flexible streaming pipe.
 *
 * @section StreamAnalyzer_Intf The user's guide
 *
 * There are two possible interfaces: a command option interface and a CLI.
 *
 * @subsection StreamAnalyzer_Intf_Opt The command option
 *
 * As many shell tools, while starting you can specify options using '-'.
 * -h will help you to know more about the potential options you can pass.
 *
 * @subsection StreamAnalyzer_Intf_Cli The CLI
 *
 * The MSStreamer implements a basic shell interface, for real time commands.
 * A prompt "cmd>" is shown.
 * Type 'help' to get more up to date information about the potential commands.
 *
 */


#include "StreamAnalyzer.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <bits/signum.h>

// Globals
StreamAnalyzer_t StreamAnalyzer;
char * Version = VERSION;

// statics
static void PrintHelp(void);
static void PrintVersion(void);
static int signalHandlerSetup(void);
static void Finalize(void);

static int Loop = 1;

/**
 * The main function
 *
 * @param argc: Argument number
 * @param argv: Argument list
 * @return -1 if error else 0
 */
int main(int argc, char *argv[])
{
    int RetVal = 0;
    opterr = 0;
    struct timeval RefTime;

    // Set the reference time for all the rest of the application
    gettimeofday(&RefTime, NULL);

    // Set defaults
	bzero(&StreamAnalyzer, sizeof(StreamAnalyzer));
    strncpy(StreamAnalyzer.OwnInetAddr, "0.0.0.0", IP_ADDR_LENGTH);

    // Setup signals
    signalHandlerSetup();

    // Parse option
     while ((RetVal = getopt (argc, argv, "o:hv")) != -1)
     {
 		switch (RetVal)
 		{
		case 'o':
			if (optarg) strncpy(StreamAnalyzer.OwnInetAddr, optarg, IP_ADDR_LENGTH);
		break;

        case 'h':
            PrintHelp();
            return(0);
        case 'v':
            PrintVersion();
            return(0);
 		break;

 		default:
 			break;
 		}
     }

	// Command input
	char CommandName[MAX_NAME_LENGTH];
	int CmdNum = 0;
	while (Loop)
	{
		// Get the function name
		printf("\ncmd> ");
		usleep(300000);
		CmdNum = scanf("%s", CommandName);

		if (CmdNum > 0)
		{
			CmdNum--;

			// HELP
			if (strcmp("help", CommandName) == 0)
			{
				PrintHelp();
				continue;
			}

			// VERSION
			if (strcmp("version", CommandName) == 0)
			{
				PrintVersion();
				continue;
			}

			// QUIT
			if (strcmp("quit", CommandName) == 0)
			{
				break;
			}

			// Our own address
			if (strcmp("o", CommandName) == 0)
			{
				CmdNum = scanf("%s", StreamAnalyzer.OwnInetAddr);
				if (CmdNum <= 0)
				{
					printf("Invalid parameter\n");
					continue;
				}

				printf("OwnInetAddr changed to %s\n", StreamAnalyzer.OwnInetAddr);

				continue;
			}

			// UDP analysis
			if (strcmp("udp", CommandName) == 0)
			{
				CmdNum = scanf("%s %i", StreamAnalyzer.Address, &StreamAnalyzer.Port);
				if (CmdNum <= 0)
				{
					printf("Invalid parameter\n");
					continue;
				}

				SA_Option_t SAOptions;
				SR_Option_t SROptions;
				SP_Option_t SPOptions;

				// Start the StreamAnalyzer
				bzero(&SAOptions, sizeof(SAOptions));
				if (StreamAnalyzer.JitterFileName[0] != 0)
				{
					strncpy(SAOptions.PcrFileName, StreamAnalyzer.JitterFileName, SA_MAX_NAME_LENGTH);
				}
				if (StreamAnalyzer.TableFileName[0] != 0)
				{
					strncpy(SAOptions.TableFileName, StreamAnalyzer.TableFileName, SA_MAX_NAME_LENGTH);
				}
				SAOptions.ReferenceTime = RefTime;
				SAOptions.MaxBufferNumber = MAX_CIRCBUFF_ELT;
				SAOptions.MaxBufferSize = MAX_CHUNK_SIZE;

				RetVal = SA_Create(&StreamAnalyzer.SA, SAOptions);
				if (RetVal != OK)
				{
					snprintf(StreamAnalyzer.ErrorString, MAX_NAME_LENGTH, "Cannot create SA: Error %i\n", RetVal);
					printf("%s", StreamAnalyzer.ErrorString);
				}

				// Start the StreamReader
				bzero(&SROptions, sizeof(SROptions));
				SROptions.InputType = Udp;
				strncpy(SROptions.RcvAddress, StreamAnalyzer.Address, IP_ADDR_LENGTH);
				SROptions.RcvPort = StreamAnalyzer.Port;
				strncpy(SROptions.BindAddress, StreamAnalyzer.OwnInetAddr, IP_ADDR_LENGTH);
				SROptions.CB = StreamAnalyzer.SA->CircBuffer;
				SROptions.ReferenceTime = RefTime;

				RetVal = SR_Create(&StreamAnalyzer.SR, SROptions);
				if (RetVal != OK)
				{
					snprintf(StreamAnalyzer.ErrorString, MAX_NAME_LENGTH, "Cannot create SR: Error %i\n", RetVal);
					printf("%s", StreamAnalyzer.ErrorString);
				}

				// Start the StreamPlayer
				bzero(&SPOptions, sizeof(SPOptions));
				if (StreamAnalyzer.DumpFileName[0] != 0)
				{
					SPOptions.OutputType = File;
					strncpy(SPOptions.FileName, StreamAnalyzer.DumpFileName, SP_MAX_NAME_LENGTH);
				}
				else
				{
					SPOptions.OutputType = Null;
				}
				SPOptions.CB = StreamAnalyzer.SA->CircBuffer;
				SPOptions.ReferenceTime = RefTime;

				RetVal = SP_Create(&StreamAnalyzer.SP, SPOptions);
				if (RetVal != OK)
				{
					snprintf(StreamAnalyzer.ErrorString, MAX_NAME_LENGTH, "Cannot create SP: Error %i\n", RetVal);
					printf("%s", StreamAnalyzer.ErrorString);
				}

				continue;
			}

			// TCP analysis
			if (strcmp("tcp", CommandName) == 0)
			{
				CmdNum = scanf("%s %i", StreamAnalyzer.Address, &StreamAnalyzer.Port);
				if (CmdNum <= 0)
				{
					printf("Invalid parameter\n");
					continue;
				}

				SA_Option_t SAOptions;
				SR_Option_t SROptions;
				SP_Option_t SPOptions;

				// Start the StreamAnalyzer
				bzero(&SAOptions, sizeof(SAOptions));
				if (StreamAnalyzer.JitterFileName[0] != 0)
				{
					strncpy(SAOptions.PcrFileName, StreamAnalyzer.JitterFileName, SA_MAX_NAME_LENGTH);
				}
				if (StreamAnalyzer.TableFileName[0] != 0)
				{
					strncpy(SAOptions.TableFileName, StreamAnalyzer.TableFileName, SA_MAX_NAME_LENGTH);
				}
				SAOptions.ReferenceTime = RefTime;
				SAOptions.MaxBufferNumber = MAX_CIRCBUFF_ELT;
				SAOptions.MaxBufferSize = MAX_CHUNK_SIZE;

				RetVal = SA_Create(&StreamAnalyzer.SA, SAOptions);
				if (RetVal != OK)
				{
					snprintf(StreamAnalyzer.ErrorString, MAX_NAME_LENGTH, "Cannot create SA: Error %i\n", RetVal);
					printf("%s", StreamAnalyzer.ErrorString);
				}

				// Start the StreamReader
				bzero(&SROptions, sizeof(SROptions));
				SROptions.InputType = Tcp;
				strncpy(SROptions.RcvAddress, StreamAnalyzer.Address, IP_ADDR_LENGTH);
				SROptions.RcvPort = StreamAnalyzer.Port;
				strncpy(SROptions.BindAddress, StreamAnalyzer.OwnInetAddr, IP_ADDR_LENGTH);
				SROptions.CB = StreamAnalyzer.SA->CircBuffer;
				SROptions.ReferenceTime = RefTime;

				RetVal = SR_Create(&StreamAnalyzer.SR, SROptions);
				if (RetVal != OK)
				{
					snprintf(StreamAnalyzer.ErrorString, MAX_NAME_LENGTH, "Cannot create SR: Error %i\n", RetVal);
					printf("%s", StreamAnalyzer.ErrorString);
				}

				// Start the StreamPlayer
				bzero(&SPOptions, sizeof(SPOptions));
				if (StreamAnalyzer.DumpFileName[0] != 0)
				{
					SPOptions.OutputType = File;
					strncpy(SPOptions.FileName, StreamAnalyzer.DumpFileName, SP_MAX_NAME_LENGTH);
				}
				else
				{
					SPOptions.OutputType = Null;
				}
				SPOptions.CB = StreamAnalyzer.SA->CircBuffer;
				SPOptions.ReferenceTime = RefTime;

				RetVal = SP_Create(&StreamAnalyzer.SP, SPOptions);
				if (RetVal != OK)
				{
					snprintf(StreamAnalyzer.ErrorString, MAX_NAME_LENGTH, "Cannot create SP: Error %i\n", RetVal);
					printf("%s", StreamAnalyzer.ErrorString);
				}

				continue;
			}

			// File analysis
			if (strcmp("file", CommandName) == 0)
			{
				CmdNum = scanf("%s", StreamAnalyzer.InputFileName);
				if (CmdNum <= 0)
				{
					printf("Invalid parameter\n");
					continue;
				}

				SA_Option_t SAOptions;
				SR_Option_t SROptions;
				SP_Option_t SPOptions;

				// Start the StreamAnalyzer
				bzero(&SAOptions, sizeof(SAOptions));
				if (StreamAnalyzer.JitterFileName[0] != 0)
				{
					strncpy(SAOptions.PcrFileName, StreamAnalyzer.JitterFileName, SA_MAX_NAME_LENGTH);
				}
				if (StreamAnalyzer.TableFileName[0] != 0)
				{
					strncpy(SAOptions.TableFileName, StreamAnalyzer.TableFileName, SA_MAX_NAME_LENGTH);
				}
				SAOptions.ReferenceTime = RefTime;
				SAOptions.MaxBufferNumber = MAX_CIRCBUFF_ELT;
				SAOptions.MaxBufferSize = MAX_CHUNK_SIZE;

				RetVal = SA_Create(&StreamAnalyzer.SA, SAOptions);
				if (RetVal != OK)
				{
					snprintf(StreamAnalyzer.ErrorString, MAX_NAME_LENGTH, "Cannot create SA: Error %i\n", RetVal);
					printf("%s", StreamAnalyzer.ErrorString);
				}

				// Start the StreamReader
				bzero(&SROptions, sizeof(SROptions));
				SROptions.InputType = File;
				strncpy(SROptions.FileName[0], StreamAnalyzer.InputFileName, SR_MAX_NAME_LENGTH);
				SROptions.CB = StreamAnalyzer.SA->CircBuffer;
				SROptions.ReferenceTime = RefTime;

				RetVal = SR_Create(&StreamAnalyzer.SR, SROptions);
				if (RetVal != OK)
				{
					snprintf(StreamAnalyzer.ErrorString, MAX_NAME_LENGTH, "Cannot create SR: Error %i\n", RetVal);
					printf("%s", StreamAnalyzer.ErrorString);
				}

				// Start the StreamPlayer
				bzero(&SPOptions, sizeof(SPOptions));
				if (StreamAnalyzer.DumpFileName[0] != 0)
				{
					SPOptions.OutputType = File;
					strncpy(SPOptions.FileName, StreamAnalyzer.DumpFileName, SP_MAX_NAME_LENGTH);
				}
				else
				{
					SPOptions.OutputType = Null;
				}
				SPOptions.CB = StreamAnalyzer.SA->CircBuffer;
				SPOptions.ReferenceTime = RefTime;

				RetVal = SP_Create(&StreamAnalyzer.SP, SPOptions);
				if (RetVal != OK)
				{
					snprintf(StreamAnalyzer.ErrorString, MAX_NAME_LENGTH, "Cannot create SP: Error %i\n", RetVal);
					printf("%s", StreamAnalyzer.ErrorString);
				}

				continue;
			}

			// Stop
			if (strcmp("stop", CommandName) == 0)
			{
				if (StreamAnalyzer.SP != NULL)
				{
					printf("Total byte analyzed: %lli\n", StreamAnalyzer.SP->TotalBytePlayed);
				}

				// Delete the triplette
				SP_Delete(StreamAnalyzer.SP);
			    SR_Delete(StreamAnalyzer.SR);
				SA_Delete(StreamAnalyzer.SA);

				StreamAnalyzer.SA = NULL;
				StreamAnalyzer.SR = NULL;
				StreamAnalyzer.SP = NULL;

				continue;
			}

			// List
			if (strcmp("l", CommandName) == 0)
			{
				RetVal = SA_Print(StreamAnalyzer.SA, "StreamAnalyzer", PRINT_LEVEL_BASIC, stdout);
				if (RetVal != OK)
				{
					snprintf(StreamAnalyzer.ErrorString, MAX_NAME_LENGTH, "Cannot list: Error %i\n", RetVal);
					printf("%s", StreamAnalyzer.ErrorString);
				}

				continue;
			}

			// Print a session
			if (strcmp("li", CommandName) == 0)
			{
				RetVal = SA_Print(StreamAnalyzer.SA, "StreamAnalyzer", PRINT_LEVEL_INTERMEDIATE, stdout);
				if (RetVal != OK)
				{
					snprintf(StreamAnalyzer.ErrorString, MAX_NAME_LENGTH, "Cannot list: Error %i\n", RetVal);
					printf("%s", StreamAnalyzer.ErrorString);
				}

				continue;
			}

			// Print a session in more details
			if (strcmp("ll", CommandName) == 0)
			{
				RetVal = SA_Print(StreamAnalyzer.SA, "StreamAnalyzer", PRINT_LEVEL_INTERNALS, stdout);
				if (RetVal != OK)
				{
					snprintf(StreamAnalyzer.ErrorString, MAX_NAME_LENGTH, "Cannot list: Error %i\n", RetVal);
					printf("%s", StreamAnalyzer.ErrorString);
				}

				continue;
			}


			// Table file
			if (strcmp("table", CommandName) == 0)
			{
				CmdNum = scanf("%s", StreamAnalyzer.TableFileName);
				if (CmdNum <= 0)
				{
					printf("Invalid parameter\n");
					continue;
				}

				continue;
			}

			// Dump file
			if (strcmp("dump", CommandName) == 0)
			{
				CmdNum = scanf("%s", StreamAnalyzer.DumpFileName);
				if (CmdNum <= 0)
				{
					printf("Invalid parameter\n");
					continue;
				}

				continue;
			}

			// Jitter file
			if (strcmp("pcr", CommandName) == 0)
			{
				CmdNum = scanf("%s", StreamAnalyzer.JitterFileName);
				if (CmdNum <= 0)
				{
					printf("Invalid parameter\n");
					continue;
				}

				continue;
			}

		// If we reach this line, we either have a bad input or we did not find any matching commands
		printf("Invalid command\n");
		}
	}

	if (StreamAnalyzer.SP != NULL)
	{
		printf("Total byte analyzed: %lli\n", StreamAnalyzer.SP->TotalBytePlayed);
	}

	Finalize();

	printf("Bye bye!\n");

	return 0;
}

/**
 * Close everything
 */
static void Finalize(void)
{
	if (StreamAnalyzer.SA != NULL)
	{
		SA_Delete(StreamAnalyzer.SA);
	}

	if (StreamAnalyzer.SR != NULL)
	{
		SR_Delete(StreamAnalyzer.SR);
	}

	if (StreamAnalyzer.SP != NULL)
	{
		SP_Delete(StreamAnalyzer.SP);
	}
}

/**
 * Handles signals
 * @param signum: Signal number
 */
void signalHandler(int signum)
{
	switch (signum)
    {
    case SIGTERM:
    case SIGKILL:
    case SIGINT:
        Loop = 0;
        close(STDIN_FILENO); // Kill scanf
        break;
    }
}

/**
 * Setup signal handler
 * @return -1 if error, 0 else
 */
static int signalHandlerSetup(void)
{
    if ((signal(SIGTERM, &signalHandler) == SIG_ERR) || (signal(SIGINT, &signalHandler) == SIG_ERR))
    {
        return -1;
    }

    return 0;
}


/**
 * Print the usage and help
 */
static void PrintHelp(void)
{
	printf(	"StreamAnalyzer receives/analyzes streams.\n"
			"\n"
			"\tUsage: StreamAnalyzer [options]\n"
			"\tExample: StreamAnalyzer -o 10.0.0.1 \n"
			"\n"
			"\tOptions:\n"
			"\t\t-o  address       Bind address (default: 0.0.0.0).\n"
            "\t\t-h                Display this message.\n"
            "\t\t-v                Display the version (Release number).\n"
			"\n"
			"\tCommands:\n"
			"\t\tquit              Close the streams and quit application.\n"
			"\t\thelp              Display this message.\n"
	        "\t\tversion           Display the version (Release number).\n"
			"\t\to address         Bind address (default: 0.0.0.0).\n"
			"\t\tudp address port  Start analyzing UDP stream.\n"
			"\t\ttcp address port  Start analyzing TCP stream.\n"
			"\t\tfile filename     Start analyzing a TS file.\n"
			"\t\tstop              Stop analyzing.\n"
			"\t\tl                 List.\n"
			"\t\tli                List intermediate details.\n"
			"\t\tll                List more details.\n"
			"\t\tTable file        Set the table file (that will contain PAT, PMT...).\n"
			"\t\tpcr file          Set the pcr file (That will contain PCR packets description).\n"
			"\t\tdump file         Set the dump file (That will contain the stream).\n"
			"\n"
			);
}

/**
 * Print the version of StreamAnalyzer
 */
static void PrintVersion(void)
{
    printf( "Version: %1$s\n"
            "Author: Luc Fourestier\n"
            "Copyright: 2010\n",
            Version
            );
}

